<?php

namespace lc\wechat\auth\Gateways;

use lc\cache\redis\Redis;
use lc\helpers\Curl;
use think\facade\Db;

class General extends Base
{
    private $appId;
    private $secret;

    public function __construct()
    {
        parent::__construct();

        $this->appId  = $this->config['appid'];
        $this->secret = $this->config['secret'];
    }

    /**
     * @link    第一步 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
     * @desc    获取授权地址
     * @param   string      $scope      授权类型snsapi_base|snsapi_userinfo
     * @param   string|null $redirect   授权回跳地址
     * @return  string
     */
    public function authUrl(string $scope = 'snsapi_base', string $redirect = null): string
    {
        $redirect = $redirect ? $redirect : "https://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
        $query = http_build_query([
            'appid'         => $this->appId,
            'redirect_uri'  => $redirect,
            'response_type' => 'code',
            'scope'         => $scope
        ]);
        return "https://open.weixin.qq.com/connect/oauth2/authorize?{$query}#wechat_redirect";
    }

    /**
     * @link    第二步 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
     * @desc    使用授权code换取openid
     * @param   string $code
     * @return  array
     */
    public function code2Openid(string $code): array
    {
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token';
        $res = Curl::requestCurl($url, 'GET', [], http_build_query([
            'appid'      => $this->appId,
            'secret'     => $this->secret,
            'code'       => $code,
            'grant_type' => 'authorization_code'
        ]));

        return $res ? json_decode($res, true) : [];
    }

    /**
     * @link    第四步 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
     * @desc    获取用户信息
     * @param   string    $accessToken
     * @param   string    $openid
     * @return  bool|mixed|string
     */
    public function userSimpleInfo(string $accessToken, string $openid): array
    {
        $url = 'https://api.weixin.qq.com/sns/userinfo';
        $res = Curl::requestCurl($url, 'GET', [], http_build_query([
            'access_token'  => $accessToken,
            'openid'        => $openid,
            'lang'          => 'zh_CN',
        ]));

        return $res ? json_decode($res, true) : [];
    }

    /**
     * @link    https://developers.weixin.qq.com/doc/offiaccount/User_Management/Get_users_basic_information_UnionID.html#UinonId
     * @desc    获取用户详细信息
     * @param   string $openid
     * @return  array
     */
    public function userDetailInfo(string $openid): array
    {
        $url = 'https://api.weixin.qq.com/cgi-bin/user/info';
        $res = Curl::requestCurl($url, 'GET', [], http_build_query([
            'access_token'  => $this->accessToken(),
            'openid'        => $openid,
            'lang'          => 'zh_CN',
        ]));

        return $res ? json_decode($res, true) : [];
    }

    /**
     * @link    附录1-JS-SDK使用权限签名算法 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html
     * @desc    获取 JSAPI TICKET
     * @param   int $repeatCnt
     * @return  string
     */
    public function jsApiTicket(&$repeatCnt = 1): string
    {
        $drive = $this->config['token_drive'] ?? 'redis';
        $key   = 'wechat_' . $this->appId . '_jsApiTicket';
        switch ($drive) {
            case 'redis':
                $redis = Redis::instance();
                $redis->select(15);

                $ticket = $redis->get($key);
                break;
            case 'db':
            default:
                $ticket = Db::connect('main')->name('configs')->where('c_key', $key)->where('c_expired > ' . time())->value('c_value');
                break;
        }
        if ($ticket) {
            return $ticket;
        }

        $url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket';
        $res = Curl::requestCurl($url, 'GET', [], http_build_query([
            'access_token'  => $this->accessToken(),
            'type'          => 'jsapi'
        ]));
        $data = json_decode($res, true);
        if (isset($data['ticket'])) {
            $ticket = $data['ticket'];
            switch ($drive) {
                case 'redis':
                    $redis->setex($key, 7000, $ticket);
                    break;
                case 'db':
                default:
                    Db::connect('main')
                        ->name('configs')
                        ->where('c_key', $key)
                        ->update([
                            'c_value'   => $ticket,
                            'c_expired' => time() + 7000,
                            'c_updated' => date('Y-m-d H:i:s')
                        ]);
                    break;
            }

            return $ticket;
        } else {
            if ($repeatCnt <= 3) {
                $repeatCnt++;
                return $this->jsApiTicket($repeatCnt);
            } else {
                return $data['errcode'];
            }
        }
    }

    /**
     * @link    https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
     * @desc    获取全局唯一接口调用凭据
     * @param   int $repeatCnt
     * @return  string
     */
    public function accessToken(&$repeatCnt = 1)
    {
        $drive = $this->config['token_drive'] ?? 'redis';
        $key   = 'wechat_' . $this->appId . '_accessToken';
        switch ($drive) {
            case 'redis':
                $redis = Redis::instance();
                $redis->select(15);

                $token = $redis->get($key);
                break;
            case 'db':
            default:
                $token = Db::connect('main')->name('configs')->where('c_key', $key)->where('c_expired > ' . time())->value('c_value');
                break;
        }
        if ($token) {
            return $token;
        }

        $url = 'https://api.weixin.qq.com/cgi-bin/token';
        $res = Curl::requestCurl($url, 'GET', [], http_build_query([
            'grant_type' => 'client_credential',
            'appid'      => $this->appId,
            'secret'     => $this->secret
        ]));

        $data = json_decode($res, true);
        if (isset($data['access_token'])) {
            $token = $data['access_token'];
            switch ($drive) {
                case 'redis':
                    $redis->setex($key, 7000, $token);
                    break;
                case 'db':
                default:
                    Db::connect('main')
                        ->name('configs')
                        ->where('c_key', $key)
                        ->update([
                            'c_value'   => $token,
                            'c_expired' => time() + 7000,
                            'c_updated' => date('Y-m-d H:i:s')
                        ]);
                    break;
            }

            return $token;
        } else {
            if ($repeatCnt <= 3) {
                $repeatCnt++;
                return $this->accessToken($repeatCnt);
            } else {
                return $data['errcode'];
            }
        }
    }
}
